import {
  type App,
  ExtractPublicPropTypes,
  PropType,
  Suspense,
  computed,
  defineComponent,
  h,
  ref,
  toRefs,
  watch,
} from 'vue';
import {
  Button,
  Col,
  ConfigProvider,
  Flex,
  Form,
  FormItem,
  Row,
  Space,
  Spin,
} from 'ant-design-vue';
import type { ColProps, FormInstance, FormProps } from 'ant-design-vue';
import { formProps } from 'ant-design-vue/es/form';
import { NamePath } from 'ant-design-vue/es/form/interface';
import { Gutter } from 'ant-design-vue/es/grid/Row';
import { omit } from 'lodash-es';
import { ItemProps } from './interface';
import { findDifferences, isFunction } from './utils';
import * as widgets from './widgets';

const proFormProps = () => ({
  ...formProps(),
  items: {
    type: Array as PropType<ItemProps[]>,
    default: () => [],
    required: true,
  },
  formColLayout: {
    type: Object as PropType<ColProps>,
    default: { xs: 24, sm: 24, md: 24, lg: 24 },
  },
  showSubmitButton: {
    type: Boolean,
    default: false,
  },
  onValuesChange: {
    type: Function as PropType<
      (
        changeValues: Record<string, any>,
        formState: Record<string, any>,
      ) => void
    >,
    default: () => {},
  },
  modalType: {
    type: String,
    default: 'disabled',
  },
});

const props = proFormProps();

type ProFormProps = ExtractPublicPropTypes<typeof props>;

const ProForm = defineComponent({
  name: 'ProForm',
  emits: ['submit', 'cancel'],
  props: props,
  setup(props, { expose, emit, slots }) {
    const { items, formColLayout, showSubmitButton, modalType } = toRefs(props);
    const gutter: [Gutter, Gutter] = [40, 0];
    const formRef = ref<FormInstance>();
    const formState = ref<Record<string, any>>({});
    expose({
      clearValidate: (name?: NamePath) => formRef.value?.clearValidate(name),
      resetFields: (name?: NamePath) => formRef.value?.resetFields(name),
      scrollToField: (name: NamePath, options: [ScrollOptions]) =>
        formRef.value?.scrollToField(name, options),
      validate: async (name?: NamePath[]) => {
        await formRef.value?.validate(name);
        return formState.value;
      },
      validateFields: async (name?: NamePath[]) => {
        await formRef.value?.validateFields(name);
        return formState.value;
      },
      getFieldsValue: () => formState.value,
      setFieldsValue: (value: any) => {
        Object.assign(formState.value, value);
      },
      setFiledValue: (key: string, value: any) => {
        formState.value[key] = value;
      },
      formState,
    });

    Object.keys(props.model || {}).forEach((key) => {
      formState.value[key as string] =
        formState.value[key as string] || props.model[key];
    });
    const submit = async (name?: NamePath[]) => {
      const res = await formRef.value?.validate(name);
      emit('submit', res);
    };

    const cancel = () => {
      emit('cancel');
    };

    const _items = computed(() => {
      const result = [];
      for (const item of items.value) {
        if (item.hide !== undefined) {
          const flag =
            typeof item.hide === 'function'
              ? item.hide(formState.value, modalType.value)
              : item.hide;
          if (flag) continue;
        }
        result.push(item);
      }
      return result;
    });
    let formStateBackups = {
      ...formState.value,
    };
    const getNestedValue = (obj: any, fields: string | string[]) => {
      const fieldsArray: string[] = Array.isArray(fields)
        ? fields
        : fields.split('.');
      return fieldsArray.reduce((value, key) => {
        return value && typeof value === 'object' && key in value
          ? value[key]
          : undefined;
      }, obj);
    };
    const setNestedProperty = (
      obj: any,
      fields: string | string[],
      value: any,
    ) => {
      const fieldsArray: string[] = Array.isArray(fields)
        ? fields
        : fields.split('.');
      fieldsArray.reduce((acc, field, index) => {
        if (index === fieldsArray.length - 1) {
          acc[field] = value;
          return acc[field];
        }
        return acc[field] ?? (acc[field] = {});
      }, obj);
    };

    watch(
      formState.value,
      (newVal) => {
        const result = findDifferences(formStateBackups, newVal);
        formStateBackups = {
          ...newVal,
        };
        props.onValuesChange?.(result, formState.value);
      },
      {
        deep: true,
      },
    );

    return () => (
      <Form
        {...omit(props, 'items', 'formColLayout', 'showSubmitButton')}
        ref={formRef}
        model={formState.value}
      >
        <ConfigProvider
          theme={{
            components: {
              Button: {
                controlHeight: 32,
              },
            },
          }}
        >
          <Row gutter={gutter}>
            {_items.value.map((item) => {
              if (item.type === 'noop') {
                return <Col {...formColLayout.value}></Col>;
              }
              const {
                formItemRender,
                type,
                field,
                disabled,
                colProps = null,
                props: _props,
                ...itemProps
              } = item;

              const _type = type === 'switch' ? '_switch' : type;

              const _disabled =
                typeof disabled === 'function'
                  ? disabled?.(formState.value)
                  : disabled;

              return formItemRender ? (
                formItemRender()
              ) : (
                <Col
                  {...(colProps ? colProps : formColLayout.value)}
                  key={item.field}
                >
                  <FormItem {...itemProps} name={field as string}>
                    {{
                      default: () => {
                        return (
                          slots[field as string]?.({
                            field,
                            formState: formState.value,
                          }) || (
                            <Suspense>
                              {{
                                default: () => {
                                  return item.customRender
                                    ? item.customRender(formState.value)
                                    : h(widgets[_type], {
                                        ..._props,
                                        [_type == '_switch'
                                          ? 'checked'
                                          : 'value']: getNestedValue(
                                          formState.value,
                                          field,
                                        ),
                                        // formState.value[field as string],
                                        [_type == '_switch'
                                          ? 'onUpdate:checked'
                                          : 'onUpdate:value']: (value: any) => {
                                          // formState.value[field as string] =
                                          //   value;
                                          setNestedProperty(
                                            formState.value,
                                            field,
                                            value,
                                          );
                                        },
                                        disabled:
                                          _props?.['disabled'] || _disabled,
                                      });
                                },
                                fallback: () => {
                                  return <Spin></Spin>;
                                },
                              }}
                            </Suspense>
                          )
                        );
                      },
                    }}
                  </FormItem>
                </Col>
              );
            })}
            {showSubmitButton.value && (
              <Col {...formColLayout.value} offset={props.labelCol.span}>
                <Flex justify='flex-start'>
                  <Space>
                    <Button onClick={() => cancel()}>取消</Button>
                    <Button type='primary' onClick={() => submit()}>
                      提交
                    </Button>
                  </Space>
                </Flex>
              </Col>
            )}
          </Row>
        </ConfigProvider>
      </Form>
    );
  },
});

export { type ProFormProps, ProForm };
